#include "WindowsService.h"

NahidaProject::WindowsService* NahidaProject::WindowsService::instance;

NahidaProject::WindowsService::WindowsService(std::string _name, bool _canPauseContinue) :
	name(_name), canPauseContinue(_canPauseContinue) {
	Wname = const_cast<char*>(name.c_str());

	status = { 0 };
	statusHandle = NULL;

	stopEvent = INVALID_HANDLE_VALUE;
	pauseEvent = INVALID_HANDLE_VALUE;
	continueEvent = INVALID_HANDLE_VALUE;
}

int NahidaProject::WindowsService::Run() {
	instance = this;

	SERVICE_TABLE_ENTRY serviceTable[] = { { Wname, (LPSERVICE_MAIN_FUNCTION)ServiceMain},{ NULL, NULL } };

	if (StartServiceCtrlDispatcher(serviceTable) == FALSE) {
		DWORD serviceDispatchError = GetLastError();
		if (serviceDispatchError == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT) {
			OnFailedRegistration();
		}
		else {
			return serviceDispatchError;
		}
	}

	return 0;
}

void WINAPI NahidaProject::WindowsService::ServiceMain() {
	// Register our service control handler with the SCM
	instance->statusHandle = RegisterServiceCtrlHandler(instance->Wname, instance->ControlHandler);

	if (instance->statusHandle == NULL) {
		return;
	}

	instance->Startup();

	// Start a thread that will perform the main task of the service
	HANDLE hThread = CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL);

	// Wait until our worker thread exits signaling that the service needs to stop
	WaitForSingleObject(hThread, INFINITE);

	instance->Exit();
}

void WINAPI NahidaProject::WindowsService::ControlHandler(DWORD CtrlCode)
{
	switch (CtrlCode) {
	case SERVICE_CONTROL_STOP:
		if (instance->status.dwCurrentState == SERVICE_PAUSED) {
			instance->ControlStopOnPause();
			break;
		}

		if (instance->status.dwCurrentState != SERVICE_RUNNING) { break; }
		instance->ControlStop();
		break;

	case SERVICE_CONTROL_PAUSE:
		if (instance->status.dwCurrentState != SERVICE_RUNNING) { break; }
		instance->ControlPause();
		break;

	case SERVICE_CONTROL_CONTINUE:
		if (instance->status.dwCurrentState != SERVICE_PAUSED) { break; }
		instance->ControlContinue();
		break;

	case SERVICE_CONTROL_SHUTDOWN:
		if (instance->status.dwCurrentState != SERVICE_RUNNING) { break; }
		instance->ControlStop();
		break;

	case SERVICE_CONTROL_INTERROGATE: // Deprecated, but you never know... let's just handle it.
		SetServiceStatus(instance->statusHandle, &instance->status);
		break;

	default:
		break;
	}
}

DWORD WINAPI NahidaProject::WindowsService::WorkerThread(LPVOID lpParam) {
	return instance->Worker(lpParam);
}

void NahidaProject::WindowsService::Startup() {
	ZeroMemory(&status, sizeof(status));
	status.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
	SetAcceptedControls(false);
	status.dwServiceSpecificExitCode = 0;
	SetState(SERVICE_START_PENDING);

	OnStartup();

	stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	pauseEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	continueEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

	if (stopEvent == NULL) {
		OnError();
		return;
	}

	SetAcceptedControls(true);

	SetState(SERVICE_RUNNING);
}

void NahidaProject::WindowsService::Exit() {
	OnExit();
	CloseHandle(stopEvent);
	CloseHandle(pauseEvent);
	CloseHandle(continueEvent);
	CloseHandle(workerPaused);
	CloseHandle(workerContinued);
	SetState(SERVICE_STOPPED);
}

void NahidaProject::WindowsService::OnError() {
	SetAcceptedControls(false);
	status.dwCurrentState = SERVICE_STOPPED;
	status.dwWin32ExitCode = GetLastError();
	status.dwCheckPoint = 0;

	if (SetServiceStatus(statusHandle, &status) == FALSE)
	{
		std::string debugmsg = name + ": service_main: SetServiceStatus returned an error";
		OutputDebugString(debugmsg.c_str());
	}
}

void NahidaProject::WindowsService::ControlStop() {
	SetState(SERVICE_STOP_PENDING);
	OnStop();
	SetAcceptedControls(false);
	SetEvent(stopEvent);
}

void NahidaProject::WindowsService::ControlPause() {
	SetState(SERVICE_PAUSE_PENDING);
	OnPause();
	SetEvent(pauseEvent);
	WaitForSingleObject(workerPaused, INFINITE);
	SetState(SERVICE_PAUSED);
}

void NahidaProject::WindowsService::ControlContinue() {
	SetState(SERVICE_CONTINUE_PENDING);
	SetAcceptedControls(false);
	ResetEvent(pauseEvent);
	OnContinue();
	SetEvent(continueEvent);
	WaitForSingleObject(workerContinued, INFINITE);
	SetAcceptedControls(true);
	ResetEvent(continueEvent);
	SetState(SERVICE_RUNNING);
}

void NahidaProject::WindowsService::ControlStopOnPause() {
	SetAcceptedControls(false);
	SetState(SERVICE_STOP_PENDING);
	OnContinue();
	SetEvent(stopEvent);
	SetEvent(continueEvent);
}

void NahidaProject::WindowsService::SetState(DWORD state) {
	status.dwCurrentState = state;
	status.dwWin32ExitCode = 0;
	status.dwCheckPoint = 0;
	status.dwWaitHint = 0;
	SetServiceStatus(statusHandle, &status);
	if (SetServiceStatus(statusHandle, &status) == FALSE) {
		std::string debugmsg = name + ": service_main: SetServiceStatus returned an error";
		OutputDebugString(debugmsg.c_str());
	}
}

void NahidaProject::WindowsService::SetAcceptedControls(bool _in) {
	if (_in) {
		status.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
		if (canPauseContinue) {
			status.dwControlsAccepted |= SERVICE_ACCEPT_PAUSE_CONTINUE;
		}
	}
	else {
		status.dwControlsAccepted = 0;
	}

	if (SetServiceStatus(statusHandle, &status) == FALSE) {
		std::string debugmsg = name + ": service_main: SetServiceStatus change accepted controls operation failed";
		OutputDebugString(debugmsg.c_str());
	}
}

void NahidaProject::WindowsService::Bump() {
	status.dwCheckPoint++;
	if (SetServiceStatus(statusHandle, &status) == FALSE) {
		std::string debugmsg = name + ": service_main: SetServiceStatus dwCheckPoint operation failed";
		OutputDebugString(debugmsg.c_str());
	}
}

void NahidaProject::WindowsService::TestStartStop() {
	std::cout << "START_PENDING" << std::endl;
	OnStartup();

	stopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	pauseEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	continueEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	HANDLE hThread = CreateThread(NULL, 0, WorkerThread, NULL, 0, NULL);
	std::cout << "RUNNING" << std::endl;

	std::cout << "Press enter to simulate SCM stop event..." << std::endl;
	std::cin.ignore();

	std::cout << "STOP_PENDING" << std::endl;
	OnStop();
	SetEvent(stopEvent);
	WaitForSingleObject(hThread, INFINITE);
	std::cout << "STOPPED" << std::endl;
}